﻿namespace Contoso.ActiveDirectory {

    #region Usings
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.DirectoryServices;
    using System.Linq;
    using System.Text;
    using System.Threading; 
    #endregion

    /// <summary>
    /// Objects that are computer accounts
    /// </summary>
    [Serializable]
    public class ComputerAccount : UserGroupComputer, IComputerAccount {

        #region Members
        #region Fields
        public static readonly new string[] AttributesToGet;

        public static readonly string ObjectCategoryShortName = "computer"; 
        #endregion

        public string DnsHostName { get; private set; }

        public new string DomainPlusName {
            get {
                if (!string.IsNullOrWhiteSpace(this.Domain)) {
                    if (!string.IsNullOrWhiteSpace(this.SamAccountNameFriendly)) {
                        return string.Format(@"{0}\{1}", this.Domain, this.SamAccountNameFriendly);
                    }
                    if (!string.IsNullOrWhiteSpace(this.Name)) {
                        return string.Format(@"{0}\{1}", this.Domain, this.Name);
                    }
                }

                return string.Empty;
            }
        }

        public IReadOnlyList<FVERecoveryInfo> FVERecoveryInfoVolumes {
            get {
                if (fveRecoveryInfoVolumes == null) {
                    fveRecoveryInfoVolumes = new List<FVERecoveryInfo>();

                    var volumes = ADDataAccess.GetADObjectProperties<FVERecoveryInfo>(
                        this.DN, null, "objectCategory=" + FVERecoveryInfo.ObjectCategoryShortName, PartitionScope.LocalPartition);

                    if (volumes != null) {
                        foreach (var volumeInfo in volumes) {
                            fveRecoveryInfoVolumes.Add(volumeInfo);
                        }
                    }
                }

                return fveRecoveryInfoVolumes;
            }
        }
        private List<FVERecoveryInfo> fveRecoveryInfoVolumes;

        public string ManagedBy { get; private set; }
        public string MSTPMOwnerInformation { get; private set; }

        /// <summary>
        /// This value may contain a wide variety of strings and is not standardized or consistent
        /// </summary>
        /// <example>
        /// Acopia ARX
        /// CentOS
        /// CentOS Linux
        /// Data Domain OS
        /// EMC Celerra File Server
        /// EMC File Server
        /// F5 ARX
        /// Kazeon Information Server Software
        /// Hyper-Vâ„¢ Server
        /// Hyper-V Server 2012
        /// Linux
        /// Mac OS X
        /// OnTap
        /// RHEL
        /// Samba
        /// SLES
        /// unknown (could be Likewise Identity 5.3.0)
        /// Windows 2000 Server
        /// Windows 7 Enterprise
        /// Windows 7 Enterprise N
        /// Windows 7 Ultimate
        /// Windows 8 Enterprise
        /// Windows 8 Pro
        /// Windows 8.1 Enterprise
        /// Windows 8.1 Pro
        /// Windows Embedded Standard
        /// Windows NT
        /// Windows Server® 2008 Enterprise
        /// Windows Server® 2008 Standard
        /// Windows ServerÂ® 2008 Enterprise
        /// Windows ServerÂ® 2008 Standard
        /// Windows ServerÂ® 2008 Standard without Hyper-V
        /// Windows Server 2003
        /// Windows Server 2008 R2 Enterprise
        /// Windows Server 2008 R2 Standard
        /// Windows Server 2012 Datacenter
        /// Windows Server 2012 R2 Standard
        /// Windows Server 2012 Release Candidate Datacenter
        /// Windows Server 2012 Standard
        /// Windows Server
        /// Windows Storage Server 2008 R2 Essentials
        /// Windows Workstation
        /// Windows Vista™ Business
        /// Windows XP Professional
        /// </example>
        public string OperatingSystem { get; private set; }
        public string OperatingSystemVersion { get; private set; }
        public string OperatingSystemVersionFriendly { get { return this.OperatingSystemVersion.Replace("(", string.Empty).Replace(")", string.Empty).Replace(" ", "."); } }
        public string OperatingSystemServicePack { get; private set; }
        public string SamAccountNameFriendly { get { return this.SamAccountName.Replace("$", string.Empty); } }

        #region IADUserComputer properties

        #region Logon and Password DateTimes
        public DateTime? AccountExpires { get; private set; }
        public DateTime? AccountExpiresUTC { get; private set; }

        public DateTime? BadPasswordTime { get; private set; }
        public DateTime? BadPasswordTimeUTC { get; private set; }

        /// <summary>
        /// LastLogonTimeStamp, if it has value.  If not, use WhenChanged, if it has a valid date.  If not, use WhenCreated
        /// </summary>
        public DateTime LastActivity { get; private set; }
        public DateTime LastActivityUTC { get; private set; }

        /// <summary>
        /// Not replicated to all domain controllers.
        /// </summary>
        public DateTime? LastLogon { get; private set; }
        public DateTime? LastLogonUTC { get; private set; }

        /// <summary>
        /// Accurate to within 14 days.  Replicated to all domain controllers and global catalogs in the domain.
        /// http://blogs.technet.com/b/askds/archive/2009/04/15/the-lastlogontimestamp-attribute-what-it-was-designed-for-and-how-it-works.aspx
        /// </summary>
        public DateTime? LastLogonTimeStamp { get; private set; }
        public DateTime? LastLogonTimeStampUTC { get; private set; }

        /// <summary>
        /// The datetime the password for the account was set
        /// </summary>
        public DateTime? PwdLastSet { get; private set; }
        public DateTime? PwdLastSetUTC { get; private set; }
        #endregion

        public bool IsAccountDisabled {
            get {
                return this.UserAccountControl.HasFlag(UserAccountControlFlags.AccountDisabled);
            }
        }
        public bool IsTrustedForConstrainedDelegation {
            get {
                return this.UserAccountControl.HasFlag(UserAccountControlFlags.TrustedToAuthenticateForDelegation);
            }
        }
        public bool IsTrustedForDelegation {
            get {
                return this.UserAccountControl.HasFlag(UserAccountControlFlags.TrustedForDelegation);
            }
        }

        public SupportedEncryptionTypeFlags MSDSSupportedEncryptionTypes { get; private set; }

        /// <summary>
        /// msDS-User-Account-Control-Computed is much like userAccountControl, but the attribute's value can contain additional bits
        /// that are not persisted. The computed bits include the following:
        /// 0x00000010 UF_LOCKOUT
        /// 0x00800000 UF_PASSWORD_EXPIRED
        /// 0x04000000 UF_PARTIAL_SECRETS_ACCOUNT
        /// 0x08000000 UF_USE_AES_KEYS
        /// Constructed attribute
        /// </summary>
        public UserAccountControlFlags MSDSUserAccountControlComputed { get; private set; }

        public UserAccountControlFlags UserAccountControl { get; private set; }

        public PrimaryGroup PrimaryGroup { get; private set; }

        #region Collections
        public IReadOnlyList<string> ServicePrincipalName { get; private set; }

        public IReadOnlyList<string> MSDSAllowedToDelegateTo { get; private set; }
        #endregion

        #endregion

        #region Active Directory domain controller attributes

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/cc220288%28v=prot.10%29.aspx
        /// For an Active Directory instance, this attribute identifies the state of the global catalog (GC) on the directory system agent (DSA).
        /// </summary>
        /// <remarks>Constructed</remarks>
        public bool MSDSIsGC { get; private set; }

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/cc220290%28v=prot.10%29.aspx
        /// For an Active Directory instance, this attribute identifies whether the DSA is a read-only DSA.
        /// </summary>
        /// <remarks>Constructed</remarks>
        public bool MSDSIsRODC { get; private set; }

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/cc220317%28v=prot.10%29.aspx
        /// For an Active Directory instance, this attribute identifies the security group whose users never have their secrets disclosed to that instance.
        /// </summary>
        public IReadOnlyList<string> MSDSNeverRevealGroup { get; private set; }
        
        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/cc220364%28v=prot.10%29.aspx
        /// For an Active Directory instance, this attribute identifies the security group whose users may have their secrets disclosed to that instance.
        /// </summary>
        
        public IReadOnlyList<string> MSDSRevealOnDemandGroup { get; private set; }
        /// <summary>
        /// For Vista/2008 and later, specifies the encryption types that may be used when encrypting a Kerberos ticket
        /// </summary>
        #endregion
        #endregion

        #region Constructors
        static ComputerAccount() {
            var attributesToGet = new List<string>();

            attributesToGet.AddRange(new string[]
            {
                "accountExpires",
                "badPasswordTime",
                "dnsHostName",
                "lastLogon",
                "lastLogonTimeStamp",
                "managedBy",
                "msDS-AllowedToDelegateTo",
                "msDS-IsGC",
                "msDS-IsRODC",
                "msDS-NeverRevealGroup",
                "msDS-RevealOnDemandGroup",
                "msDS-SupportedEncryptionTypes",
                "msDS-User-Account-Control-Computed",
                "msTPM-OwnerInformation",
                "operatingSystem",
                "operatingSystemVersion",
                "operatingSystemServicePack",
                "primaryGroupID",
                "pwdLastSet",
                "servicePrincipalName",
                "userAccountControl"
            });

            attributesToGet.AddRange(UserGroupComputer.AttributesToGet);

            AttributesToGet = attributesToGet.Distinct().ToArray();
            attributesToGet = null;
        }

        public ComputerAccount() {
            this.Initialize();
        } 

        public ComputerAccount(DirectoryEntry directoryEntry, bool destroyInputObject = true)
            : base(directoryEntry, destroyInputObject) {
                if (this.GetType().Name == typeof(ComputerAccount).Name) {
                this.Attributes = null;
            }
        }

        public ComputerAccount(SearchResult result, bool destroyInputObject = true)
            : base(result, destroyInputObject) {
                if (this.GetType().Name == typeof(ComputerAccount).Name) {
                this.Attributes = null;
            }
        }

        public ComputerAccount(List<string> ldifOutput)
            : base(ldifOutput) {
                if (this.GetType().Name == typeof(ComputerAccount).Name) {
                this.Attributes = null;
            }
        }
        #endregion

        #region Methods
        protected override void Initialize() {
            base.Initialize();

            this.DNSDomain = string.Empty;
            this.DnsHostName = string.Empty;
            this.ManagedBy = string.Empty;
            this.OperatingSystem = string.Empty;
            this.OperatingSystemVersion = string.Empty;
            this.OperatingSystemServicePack = string.Empty;
        }

        protected override void ProcessResults() {
            
            try {
                base.ProcessResults();

                if (this.Attributes.ContainsKey("dnsHostName")) this.DnsHostName = this.Attributes["dnsHostName"][0].ToString();
                if (this.Attributes.ContainsKey("managedBy")) this.ManagedBy = this.Attributes["managedBy"][0].ToString();
                if (this.Attributes.ContainsKey("operatingSystem")) this.OperatingSystem = this.Attributes["operatingSystem"][0].ToString();
                if (this.Attributes.ContainsKey("operatingSystemVersion")) this.OperatingSystemVersion = this.Attributes["operatingSystemVersion"][0].ToString();
                if (this.Attributes.ContainsKey("operatingSystemServicePack")) this.OperatingSystemServicePack = this.Attributes["operatingSystemServicePack"][0].ToString();
                if (this.Attributes.ContainsKey("msTPM-OwnerInformation")) this.MSTPMOwnerInformation = this.Attributes["msTPM-OwnerInformation"][0].ToString();

                this.MSDSNeverRevealGroup = ADDataAccess.GetMultiValuedAttribute<string>("msDS-NeverRevealGroup", this.Attributes);
                this.MSDSRevealOnDemandGroup = ADDataAccess.GetMultiValuedAttribute<string>("msDS-RevealOnDemandGroup", this.Attributes);

                #region Logon and password information
                if (this.Attributes.ContainsKey("accountExpires") && (this.Attributes["accountExpires"][0] != null)) {
                    long accountExpiresTemp = 0;
                    if (long.TryParse(this.Attributes["accountExpires"][0].ToString(), out accountExpiresTemp)) {
                        if ((accountExpiresTemp != long.MinValue) && (accountExpiresTemp != long.MaxValue)) {
                            this.AccountExpiresUTC = DateTime.FromFileTimeUtc(accountExpiresTemp);
                            this.AccountExpires = this.AccountExpiresUTC.Value.ToLocalTime();
                        }
                    }
                }
                if (this.Attributes.ContainsKey("badPasswordTime") && (this.Attributes["badPasswordTime"][0] != null)) {
                    long badPasswordTimeTemp = 0;
                    if (long.TryParse(this.Attributes["badPasswordTime"][0].ToString(), out badPasswordTimeTemp)) {
                        if ((badPasswordTimeTemp != long.MinValue) && (badPasswordTimeTemp != long.MaxValue)) {
                            this.BadPasswordTimeUTC = DateTime.FromFileTimeUtc(badPasswordTimeTemp);
                            this.BadPasswordTime = this.BadPasswordTimeUTC.Value.ToLocalTime();
                        }
                    }
                }
                if (this.Attributes.ContainsKey("lastlogon") && (this.Attributes["lastlogon"][0] != null)) {
                    long lastLogonTemp = 0;
                    if (long.TryParse(this.Attributes["lastlogon"][0].ToString(), out lastLogonTemp)) {
                        if ((lastLogonTemp != long.MinValue) && (lastLogonTemp != long.MaxValue)) {
                            this.LastLogonUTC = DateTime.FromFileTimeUtc(lastLogonTemp);
                            this.LastLogon = this.LastLogonUTC.Value.ToLocalTime();
                        }
                    }
                }
                if (this.Attributes.ContainsKey("lastlogontimestamp") && (this.Attributes["lastlogontimestamp"][0] != null)) {
                    long lastLogonTimeStampTemp = 0;
                    if (long.TryParse(this.Attributes["lastlogontimestamp"][0].ToString(), out lastLogonTimeStampTemp)) {
                        if ((lastLogonTimeStampTemp != long.MinValue) && (lastLogonTimeStampTemp != long.MaxValue)) {
                            this.LastLogonTimeStampUTC = DateTime.FromFileTimeUtc(lastLogonTimeStampTemp);
                            this.LastLogonTimeStamp = this.LastLogonTimeStampUTC.Value.ToLocalTime();
                        }
                    }
                }
                if (this.Attributes.ContainsKey("pwdLastSet") && (this.Attributes["pwdLastSet"][0] != null)) {
                    long pwdLastSetTemp = 0;
                    if (long.TryParse(this.Attributes["pwdLastSet"][0].ToString(), out pwdLastSetTemp)) {
                        if ((pwdLastSetTemp != long.MinValue) && (pwdLastSetTemp != long.MaxValue)) {
                            this.PwdLastSetUTC = DateTime.FromFileTimeUtc(pwdLastSetTemp);
                            this.PwdLastSet = this.PwdLastSetUTC.Value.ToLocalTime();
                        }
                    }
                }

                if (this.Attributes.ContainsKey("msDS-SupportedEncryptionTypes")) {
                    var msDSSupportedEncryptionTypes = SupportedEncryptionTypeFlags.Undefined;
                    if (Enum.TryParse<SupportedEncryptionTypeFlags>(
                        this.Attributes["msDS-SupportedEncryptionTypes"][0].ToString(), ignoreCase: true, result: out msDSSupportedEncryptionTypes)) {
                        this.MSDSSupportedEncryptionTypes = msDSSupportedEncryptionTypes;
                    }
                }
                if (this.Attributes.ContainsKey("primaryGroupID")) {
                    var primaryGroup = PrimaryGroup.Undefined;
                    if (Enum.TryParse<PrimaryGroup>(this.Attributes["primaryGroupID"][0].ToString(), out primaryGroup)) {
                        this.PrimaryGroup = primaryGroup;
                    }
                }

                if (this.Attributes.ContainsKey("msDS-User-Account-Control-Computed")) {
                    var userAccountControlComputed = UserAccountControlFlags.NotSet;
                    if (Enum.TryParse<UserAccountControlFlags>(this.Attributes["msDS-User-Account-Control-Computed"][0].ToString(), out userAccountControlComputed)) {
                        this.MSDSUserAccountControlComputed = userAccountControlComputed;
                    }
                }

                if (this.Attributes.ContainsKey("userAccountControl")) {
                    var userAccountControl= UserAccountControlFlags.NotSet;
                    if (Enum.TryParse<UserAccountControlFlags>(this.Attributes["userAccountControl"][0].ToString(), out userAccountControl))
                        this.UserAccountControl = userAccountControl;
                }

                if (this.Attributes.ContainsKey("msDS-IsGC")) {
                    this.MSDSIsGC = Convert.ToBoolean(this.Attributes["msDS-IsGC"][0]);
                }

                if (this.Attributes.ContainsKey("msDS-IsRODC")) {
                    this.MSDSIsRODC = Convert.ToBoolean(this.Attributes["msDS-IsRODC"][0]);
                }
                else {
                    this.MSDSIsRODC = (this.PrimaryGroup == ActiveDirectory.PrimaryGroup.ReadOnlyDomainControllers)
                        || this.UserAccountControl.HasFlag(UserAccountControlFlags.PartialSecretsAccount)
                        || this.MSDSUserAccountControlComputed.HasFlag(UserAccountControlFlags.PartialSecretsAccount);
                }

                this.ServicePrincipalName = ADDataAccess.GetMultiValuedAttribute<string>("servicePrincipalName", this.Attributes);
                this.MSDSAllowedToDelegateTo = ADDataAccess.GetMultiValuedAttribute<string>("msDS-AllowedToDelegateTo", this.Attributes);

                // lastActivity = LastLogonTimeStamp; if not available, use WhenChanged or WhenCreated
                if (this.LastLogonTimeStampUTC.HasValue) {
                    this.LastActivityUTC = this.LastLogonTimeStampUTC.Value;
                }
                else if (this.WhenChangedUTC != DateTime.MinValue) {
                    this.LastActivityUTC = this.WhenChangedUTC;
                }
                else if (this.WhenCreatedUTC != DateTime.MinValue) {
                    this.LastActivityUTC = this.WhenCreatedUTC;
                }
                if (this.LastActivityUTC != DateTime.MinValue) {
                    this.LastActivity = this.LastActivityUTC.ToLocalTime();
                }
                #endregion

                this.IsValid = !string.IsNullOrWhiteSpace(this.DN)
                    && this.ObjectClass.Any(x => string.Equals(x, ObjectCategoryShortName, StringComparison.OrdinalIgnoreCase));
            }
            catch (Exception e) {
                Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} Error: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), e.VerboseExceptionString()));
                throw;
            }
        }

        [DebuggerStepThroughAttribute]
        public override string ToString() {
            if (!string.IsNullOrWhiteSpace(this.DomainPlusName)) {
                return this.DomainPlusName;
            }
            else {
                return this.DN;
            }
        }

        [DebuggerStepThroughAttribute]
        public string ToVerboseString() {
            var info = new StringBuilder();

            info.AppendFormat("Id: {0}; ", this.ObjectGuid);
            info.AppendFormat("DN: {0}; ", this.DN ?? "NULL");
            info.AppendFormat("ADDomainDNSName: {0}; ", this.DNSDomain ?? "NULL");
            info.AppendFormat("DnsHostName: {0}; ", this.DnsHostName ?? "NULL");
            info.AppendFormat("ADDomainNetbiosName: {0}; ", this.Domain ?? "NULL");
            info.AppendFormat("OperatingSystem: {0}; ", this.OperatingSystem ?? "NULL");
            info.AppendFormat("OperatingSystemVersionFriendly: {0}; ", this.OperatingSystemVersionFriendly ?? "NULL");
            info.AppendFormat("SamAccountName: {0}; ", this.SamAccountName ?? "NULL");
            info.AppendFormat("WhenCreated: {0}; ", this.WhenCreated.YMDHMSFriendly());
            info.AppendFormat("WhenChanged: {0}", this.WhenChanged.YMDHMSFriendly());

            return info.ToString();
        }

        #endregion
    }
}
